Passed
Branch v8.x (741daf)
by Rafael S.
02:15
created

wavio.js ➔ getDs64Bytes_   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 14
c 1
b 0
f 0
nc 2
dl 0
loc 22
rs 9.7
nop 0
1
/*
2
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview A class to read and write wav data.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import riffChunks from '../vendor/riff-chunks.js';
31
import {pack, unpackFrom, unpackString,
32
  packStringTo, packTo, packString,} from '../vendor/byte-data.js';
33
34
// @type {WavStruct}
35
import WavStruct from './wavstruct.js';
36
37
/**
38
 * A class to read and write wav data.
39
 * @extends WavStruct
40
 */
41
export default class WavIO extends WavStruct {
42
43
  constructor() {
44
    super();
45
    /**
46
     * @type {!Object}
47
     * @private
48
     */
49
    this.uInt16_ = {bits: 16, be: false};
50
    /**
51
     * @type {!Object}
52
     * @private
53
     */
54
    this.uInt32_ = {bits: 32, be: false};
55
    /**
56
     * The bit depth code according to the samples.
57
     * @type {string}
58
     */
59
    this.bitDepth = '0';
60
    /**
61
     * @type {number}
62
     * @private
63
     */
64
    this.head_ = 0;
65
    /**
66
     * @type {!Object}
67
     * @private
68
     */
69
    this.dataType = {};
70
  }
71
72
  /**
73
   * Write a variable size string as bytes. If the string is smaller
74
   * than the max size the output array is filled with 0s.
75
   * @param {string} str The string to be written as bytes.
76
   * @param {number} maxSize the max size of the string.
77
   * @return {!Array<number>} The bytes.
78
   * @private
79
   */
80
  writeString_(str, maxSize, push=true) {
81
    /** @type {!Array<number>} */   
82
    let bytes = packString(str);
83
    if (push) {
84
      for (let i=bytes.length; i<maxSize; i++) {
85
        bytes.push(0);
86
      }  
87
    }
88
    return bytes;
89
  }
90
91
  /**
92
   * Return the bytes of the 'bext' chunk.
93
   * @return {!Array<number>} The 'bext' chunk bytes.
94
   * @private
95
   */
96
  getBextBytes_() {
97
    /** @type {!Array<number>} */
98
    let bytes = [];
99
    this.enforceBext_();
100
    if (this.bext.chunkId) {
101
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
102
      bytes = bytes.concat(
103
        packString(this.bext.chunkId),
104
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
105
        this.writeString_(this.bext.description, 256),
106
        this.writeString_(this.bext.originator, 32),
107
        this.writeString_(this.bext.originatorReference, 32),
108
        this.writeString_(this.bext.originationDate, 10),
109
        this.writeString_(this.bext.originationTime, 8),
110
        pack(this.bext.timeReference[0], this.uInt32_),
111
        pack(this.bext.timeReference[1], this.uInt32_),
112
        pack(this.bext.version, this.uInt16_),
113
        this.writeString_(this.bext.UMID, 64),
114
        pack(this.bext.loudnessValue, this.uInt16_),
115
        pack(this.bext.loudnessRange, this.uInt16_),
116
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
117
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
118
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
119
        this.writeString_(this.bext.reserved, 180),
120
        this.writeString_(
121
          this.bext.codingHistory, this.bext.codingHistory.length));
122
    }
123
    return bytes;
124
  }
125
126
  /**
127
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
128
   * @private
129
   */
130
  enforceBext_() {
131
    for (var prop in this.bext) {
132
      if (this.bext.hasOwnProperty(prop)) {
133
        if (this.bext[prop] && prop != 'timeReference') {
134
          this.bext.chunkId = 'bext';
135
          break;
136
        }
137
      }
138
    }
139
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
140
      this.bext.chunkId = 'bext';
141
    }
142
  }
143
144
  /**
145
   * Return the bytes of the 'ds64' chunk.
146
   * @return {!Array<number>} The 'ds64' chunk bytes.
147
   * @private
148
   */
149
  getDs64Bytes_() {
150
    /** @type {!Array<number>} */
151
    let bytes = [];
152
    if (this.ds64.chunkId) {
153
      bytes = bytes.concat(
154
        packString(this.ds64.chunkId),
155
        pack(this.ds64.chunkSize, this.uInt32_),
156
        pack(this.ds64.riffSizeHigh, this.uInt32_),
157
        pack(this.ds64.riffSizeLow, this.uInt32_),
158
        pack(this.ds64.dataSizeHigh, this.uInt32_),
159
        pack(this.ds64.dataSizeLow, this.uInt32_),
160
        pack(this.ds64.originationTime, this.uInt32_),
161
        pack(this.ds64.sampleCountHigh, this.uInt32_),
162
        pack(this.ds64.sampleCountLow, this.uInt32_));
163
    }
164
    //if (this.ds64.tableLength) {
165
    //  ds64Bytes = ds64Bytes.concat(
166
    //    pack(this.ds64.tableLength, this.uInt32_),
167
    //    this.ds64.table);
168
    //}
169
    return bytes;
170
  }
171
172
  /**
173
   * Return the bytes of the 'cue ' chunk.
174
   * @return {!Array<number>} The 'cue ' chunk bytes.
175
   * @private
176
   */
177
  getCueBytes_() {
178
    /** @type {!Array<number>} */
179
    let bytes = [];
180
    if (this.cue.chunkId) {
181
      /** @type {!Array<number>} */
182
      let cuePointsBytes = this.getCuePointsBytes_();
183
      bytes = bytes.concat(
184
        packString(this.cue.chunkId),
185
        pack(cuePointsBytes.length + 4, this.uInt32_),
186
        pack(this.cue.dwCuePoints, this.uInt32_),
187
        cuePointsBytes);
188
    }
189
    return bytes;
190
  }
191
192
  /**
193
   * Return the bytes of the 'cue ' points.
194
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
195
   * @private
196
   */
197
  getCuePointsBytes_() {
198
    /** @type {!Array<number>} */
199
    let points = [];
200
    for (let i=0; i<this.cue.dwCuePoints; i++) {
201
      points = points.concat(
202
        pack(this.cue.points[i].dwName, this.uInt32_),
203
        pack(this.cue.points[i].dwPosition, this.uInt32_),
204
        packString(this.cue.points[i].fccChunk),
205
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
206
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
207
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
208
    }
209
    return points;
210
  }
211
212
  /**
213
   * Return the bytes of the 'smpl' chunk.
214
   * @return {!Array<number>} The 'smpl' chunk bytes.
215
   * @private
216
   */
217
  getSmplBytes_() {
218
    /** @type {!Array<number>} */
219
    let bytes = [];
220
    if (this.smpl.chunkId) {
221
      /** @type {!Array<number>} */
222
      let smplLoopsBytes = this.getSmplLoopsBytes_();
223
      bytes = bytes.concat(
224
        packString(this.smpl.chunkId),
225
        pack(smplLoopsBytes.length + 36, this.uInt32_),
226
        pack(this.smpl.dwManufacturer, this.uInt32_),
227
        pack(this.smpl.dwProduct, this.uInt32_),
228
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
229
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
230
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
231
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
232
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
233
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
234
        pack(this.smpl.dwSamplerData, this.uInt32_),
235
        smplLoopsBytes);
236
    }
237
    return bytes;
238
  }
239
240
  /**
241
   * Return the bytes of the 'smpl' loops.
242
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
243
   * @private
244
   */
245
  getSmplLoopsBytes_() {
246
    /** @type {!Array<number>} */
247
    let loops = [];
248
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
249
      loops = loops.concat(
250
        pack(this.smpl.loops[i].dwName, this.uInt32_),
251
        pack(this.smpl.loops[i].dwType, this.uInt32_),
252
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
253
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
254
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
255
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
256
    }
257
    return loops;
258
  }
259
260
  /**
261
   * Return the bytes of the 'fact' chunk.
262
   * @return {!Array<number>} The 'fact' chunk bytes.
263
   * @private
264
   */
265
  getFactBytes_() {
266
    /** @type {!Array<number>} */
267
    let bytes = [];
268
    if (this.fact.chunkId) {
269
      bytes = bytes.concat(
270
        packString(this.fact.chunkId),
271
        pack(this.fact.chunkSize, this.uInt32_),
272
        pack(this.fact.dwSampleLength, this.uInt32_));
273
    }
274
    return bytes;
275
  }
276
277
  /**
278
   * Return the bytes of the 'fmt ' chunk.
279
   * @return {!Array<number>} The 'fmt' chunk bytes.
280
   * @throws {Error} if no 'fmt ' chunk is present.
281
   * @private
282
   */
283
  getFmtBytes_() {
284
    /** @type {!Array<number>} */
285
    let fmtBytes = [];
286
    if (this.fmt.chunkId) {
287
      return fmtBytes.concat(
288
        packString(this.fmt.chunkId),
289
        pack(this.fmt.chunkSize, this.uInt32_),
290
        pack(this.fmt.audioFormat, this.uInt16_),
291
        pack(this.fmt.numChannels, this.uInt16_),
292
        pack(this.fmt.sampleRate, this.uInt32_),
293
        pack(this.fmt.byteRate, this.uInt32_),
294
        pack(this.fmt.blockAlign, this.uInt16_),
295
        pack(this.fmt.bitsPerSample, this.uInt16_),
296
        this.getFmtExtensionBytes_());
297
    }
298
    throw Error('Could not find the "fmt " chunk');
299
  }
300
301
  /**
302
   * Return the bytes of the fmt extension fields.
303
   * @return {!Array<number>} The fmt extension bytes.
304
   * @private
305
   */
306
  getFmtExtensionBytes_() {
307
    /** @type {!Array<number>} */
308
    let extension = [];
309
    if (this.fmt.chunkSize > 16) {
310
      extension = extension.concat(
311
        pack(this.fmt.cbSize, this.uInt16_));
312
    }
313
    if (this.fmt.chunkSize > 18) {
314
      extension = extension.concat(
315
        pack(this.fmt.validBitsPerSample, this.uInt16_));
316
    }
317
    if (this.fmt.chunkSize > 20) {
318
      extension = extension.concat(
319
        pack(this.fmt.dwChannelMask, this.uInt32_));
320
    }
321
    if (this.fmt.chunkSize > 24) {
322
      extension = extension.concat(
323
        pack(this.fmt.subformat[0], this.uInt32_),
324
        pack(this.fmt.subformat[1], this.uInt32_),
325
        pack(this.fmt.subformat[2], this.uInt32_),
326
        pack(this.fmt.subformat[3], this.uInt32_));
327
    }
328
    return extension;
329
  }
330
331
  /**
332
   * Return the bytes of the 'LIST' chunk.
333
   * @return {!Array<number>} The 'LIST' chunk bytes.
334
   */
335
  getLISTBytes_() {
336
    /** @type {!Array<number>} */
337
    let bytes = [];
338
    for (let i=0; i<this.LIST.length; i++) {
339
      /** @type {!Array<number>} */
340
      let subChunksBytes = this.getLISTSubChunksBytes_(
341
          this.LIST[i].subChunks, this.LIST[i].format);
342
      bytes = bytes.concat(
343
        packString(this.LIST[i].chunkId),
344
        pack(subChunksBytes.length + 4, this.uInt32_),
345
        packString(this.LIST[i].format),
346
        subChunksBytes);
347
    }
348
    return bytes;
349
  }
350
351
  /**
352
   * Return the bytes of the sub chunks of a 'LIST' chunk.
353
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
354
   * @param {string} format The format of the 'LIST' chunk.
355
   *    Currently supported values are 'adtl' or 'INFO'.
356
   * @return {!Array<number>} The sub chunk bytes.
357
   * @private
358
   */
359
  getLISTSubChunksBytes_(subChunks, format) {
360
    /** @type {!Array<number>} */
361
    let bytes = [];
362
    for (let i=0; i<subChunks.length; i++) {
363
      if (format == 'INFO') {
364
        bytes = bytes.concat(
365
          packString(subChunks[i].chunkId),
366
          pack(subChunks[i].value.length + 1, this.uInt32_),
367
          this.writeString_(
368
            subChunks[i].value, subChunks[i].value.length));
369
        bytes.push(0);
370
      } else if (format == 'adtl') {
371
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
372
          bytes = bytes.concat(
373
            packString(subChunks[i].chunkId),
374
            pack(
375
              subChunks[i].value.length + 4 + 1, this.uInt32_),
376
            pack(subChunks[i].dwName, this.uInt32_),
377
            this.writeString_(
378
              subChunks[i].value,
379
              subChunks[i].value.length));
380
          bytes.push(0);
381
        } else if (subChunks[i].chunkId == 'ltxt') {
382
          bytes = bytes.concat(
383
            this.getLtxtChunkBytes_(subChunks[i]));
384
        }
385
      }
386
      if (bytes.length % 2) {
387
        bytes.push(0);
388
      }
389
    }
390
    return bytes;
391
  }
392
393
  /**
394
   * Return the bytes of a 'ltxt' chunk.
395
   * @param {!Object} ltxt the 'ltxt' chunk.
396
   * @return {!Array<number>} The 'ltxt' chunk bytes.
397
   * @private
398
   */
399
  getLtxtChunkBytes_(ltxt) {
400
    return [].concat(
401
      packString(ltxt.chunkId),
402
      pack(ltxt.value.length + 20, this.uInt32_),
403
      pack(ltxt.dwName, this.uInt32_),
404
      pack(ltxt.dwSampleLength, this.uInt32_),
405
      pack(ltxt.dwPurposeID, this.uInt32_),
406
      pack(ltxt.dwCountry, this.uInt16_),
407
      pack(ltxt.dwLanguage, this.uInt16_),
408
      pack(ltxt.dwDialect, this.uInt16_),
409
      pack(ltxt.dwCodePage, this.uInt16_),
410
      this.writeString_(ltxt.value, ltxt.value.length));
411
  }
412
413
  /**
414
   * Return the bytes of the 'junk' chunk.
415
   * @return {!Array<number>} The 'junk' chunk bytes.
416
   * @private
417
   */
418
  getJunkBytes_() {
419
    /** @type {!Array<number>} */
420
    let bytes = [];
421
    if (this.junk.chunkId) {
422
      return bytes.concat(
423
        packString(this.junk.chunkId),
424
        pack(this.junk.chunkData.length, this.uInt32_),
425
        this.junk.chunkData);
426
    }
427
    return bytes;
428
  }
429
430
  /**
431
   * Return 'RIFF' if the container is 'RF64', the current container name
432
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
433
   * @return {string}
434
   * @private
435
   */
436
  correctContainer_() {
437
    return this.container == 'RF64' ? 'RIFF' : this.container;
438
  }
439
440
  /**
441
   * Set the string code of the bit depth based on the 'fmt ' chunk.
442
   * @private
443
   */
444
  bitDepthFromFmt_() {
445
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
446
      this.bitDepth = '32f';
447
    } else if (this.fmt.audioFormat === 6) {
448
      this.bitDepth = '8a';
449
    } else if (this.fmt.audioFormat === 7) {
450
      this.bitDepth = '8m';
451
    } else {
452
      this.bitDepth = this.fmt.bitsPerSample.toString();
453
    }
454
  }
455
456
  /**
457
   * Return a .wav file byte buffer with the data from the WaveFile object.
458
   * The return value of this method can be written straight to disk.
459
   * @return {!Uint8Array} The wav file bytes.
460
   * @private
461
   */
462
  createWaveFile_() {
463
    /** @type {!Array<!Array<number>>} */
464
    let fileBody = [
465
      this.getJunkBytes_(),
466
      this.getDs64Bytes_(),
467
      this.getBextBytes_(),
468
      this.getFmtBytes_(),
469
      this.getFactBytes_(),
470
      packString(this.data.chunkId),
471
      pack(this.data.samples.length, this.uInt32_),
472
      this.data.samples,
473
      this.getCueBytes_(),
474
      this.getSmplBytes_(),
475
      this.getLISTBytes_()
476
    ];
477
    /** @type {number} */
478
    let fileBodyLength = 0;
479
    for (let i=0; i<fileBody.length; i++) {
480
      fileBodyLength += fileBody[i].length;
481
    }
482
    /** @type {!Uint8Array} */
483
    let file = new Uint8Array(fileBodyLength + 12);
484
    /** @type {number} */
485
    let index = 0;
486
    index = packStringTo(this.container, file, index);
487
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
488
    index = packStringTo(this.format, file, index);
489
    for (let i=0; i<fileBody.length; i++) {
490
      file.set(fileBody[i], index);
491
      index += fileBody[i].length;
492
    }
493
    return file;
494
  }
495
496
  /**
497
   * Update the type definition used to read and write the samples.
498
   * @private
499
   */
500
  updateDataType_() {
501
    /** @type {!Object} */
502
    this.dataType = {
503
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
504
      float: this.bitDepth == '32f' || this.bitDepth == '64',
505
      signed: this.bitDepth != '8',
506
      be: this.container == 'RIFX'
507
    };
508
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
509
      this.dataType.bits = 8;
510
      this.dataType.signed = false;
511
    }
512
  }
513
514
  /**
515
   * Set up the WaveFile object from a byte buffer.
516
   * @param {!Uint8Array} buffer The buffer.
517
   * @param {boolean=} samples True if the samples should be loaded.
518
   * @throws {Error} If container is not RIFF, RIFX or RF64.
519
   * @throws {Error} If no 'fmt ' chunk is found.
520
   * @throws {Error} If no 'data' chunk is found.
521
   */
522
  readWavBuffer(buffer, samples=true) {
523
    this.head_ = 0;
524
    this.clearHeader_();
525
    this.readRIFFChunk_(buffer);
526
    /** @type {!Object} */
527
    let chunk = riffChunks(buffer);
528
    this.readDs64Chunk_(buffer, chunk.subChunks);
529
    this.readFmtChunk_(buffer, chunk.subChunks);
530
    this.readFactChunk_(buffer, chunk.subChunks);
531
    this.readBextChunk_(buffer, chunk.subChunks);
532
    this.readCueChunk_(buffer, chunk.subChunks);
533
    this.readSmplChunk_(buffer, chunk.subChunks);
534
    this.readDataChunk_(buffer, chunk.subChunks, samples);
535
    this.readJunkChunk_(buffer, chunk.subChunks);
536
    this.readLISTChunk_(buffer, chunk.subChunks);
537
    this.bitDepthFromFmt_();
538
    this.updateDataType_();
539
  }
540
541
  /**
542
   * Return the closest greater number of bits for a number of bits that
543
   * do not fill a full sequence of bytes.
544
   * @param {string} bitDepthCode The bit depth.
545
   * @return {string}
546
   * @private
547
   */
548
  realBitDepth_(bitDepthCode) {
549
    if (bitDepthCode != '32f') {
550
      bitDepthCode = (((parseInt(bitDepthCode, 10) - 1) | 7) + 1).toString();
551
    }
552
    return bitDepthCode;
553
  }
554
555
  /**
556
   * Reset attributes that should emptied when a file is
557
   * created with the fromScratch() or fromBuffer() methods.
558
   * @private
559
   */
560
  clearHeader_() {
561
    this.fmt.cbSize = 0;
562
    this.fmt.validBitsPerSample = 0;
563
    this.fact.chunkId = '';
564
    this.ds64.chunkId = '';
565
  }
566
567
  /**
568
   * Set up to work wih big-endian or little-endian files.
569
   * The types used are changed to LE or BE. If the
570
   * the file is big-endian (RIFX), true is returned.
571
   * @return {boolean} True if the file is RIFX.
572
   * @private
573
   */
574
  LEorBE_() {
575
    /** @type {boolean} */
576
    let bigEndian = this.container === 'RIFX';
577
    this.uInt16_.be = bigEndian;
578
    this.uInt32_.be = bigEndian;
579
    return bigEndian;
580
  }
581
582
  /**
583
   * Find a chunk by its fourCC_ in a array of RIFF chunks.
584
   * @param {!Object} chunks The wav file chunks.
585
   * @param {string} chunkId The chunk fourCC_.
586
   * @param {boolean} multiple True if there may be multiple chunks
587
   *    with the same chunkId.
588
   * @return {?Array<!Object>}
589
   * @private
590
   */
591
  findChunk_(chunks, chunkId, multiple=false) {
592
    /** @type {!Array<!Object>} */
593
    let chunk = [];
594
    for (let i=0; i<chunks.length; i++) {
595
      if (chunks[i].chunkId == chunkId) {
596
        if (multiple) {
597
          chunk.push(chunks[i]);
598
        } else {
599
          return chunks[i];
600
        }
601
      }
602
    }
603
    if (chunkId == 'LIST') {
604
      return chunk.length ? chunk : null;
605
    }
606
    return null;
607
  }
608
609
  /**
610
   * Read the RIFF chunk a wave file.
611
   * @param {!Uint8Array} bytes A wav buffer.
612
   * @throws {Error} If no 'RIFF' chunk is found.
613
   * @private
614
   */
615
  readRIFFChunk_(bytes) {
616
    this.head_ = 0;
617
    this.container = this.readString_(bytes, 4);
618
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
619
      throw Error('Not a supported format.');
620
    }
621
    this.LEorBE_();
622
    this.chunkSize = this.read_(bytes, this.uInt32_);
623
    this.format = this.readString_(bytes, 4);
624
    if (this.format != 'WAVE') {
625
      throw Error('Could not find the "WAVE" format identifier');
626
    }
627
  }
628
629
  /**
630
   * Read the 'fmt ' chunk of a wave file.
631
   * @param {!Uint8Array} buffer The wav file buffer.
632
   * @param {!Object} signature The file signature.
633
   * @throws {Error} If no 'fmt ' chunk is found.
634
   * @private
635
   */
636
  readFmtChunk_(buffer, signature) {
637
    /** @type {?Object} */
638
    let chunk = this.findChunk_(signature, 'fmt ');
639
    if (chunk) {
640
      this.head_ = chunk.chunkData.start;
641
      this.fmt.chunkId = chunk.chunkId;
642
      this.fmt.chunkSize = chunk.chunkSize;
643
      this.fmt.audioFormat = this.read_(buffer, this.uInt16_);
644
      this.fmt.numChannels = this.read_(buffer, this.uInt16_);
645
      this.fmt.sampleRate = this.read_(buffer, this.uInt32_);
646
      this.fmt.byteRate = this.read_(buffer, this.uInt32_);
647
      this.fmt.blockAlign = this.read_(buffer, this.uInt16_);
648
      this.fmt.bitsPerSample = this.read_(buffer, this.uInt16_);
649
      this.readFmtExtension_(buffer);
650
    } else {
651
      throw Error('Could not find the "fmt " chunk');
652
    }
653
  }
654
655
  /**
656
   * Read the 'fmt ' chunk extension.
657
   * @param {!Uint8Array} buffer The wav file buffer.
658
   * @private
659
   */
660
  readFmtExtension_(buffer) {
661
    if (this.fmt.chunkSize > 16) {
662
      this.fmt.cbSize = this.read_(buffer, this.uInt16_);
663
      if (this.fmt.chunkSize > 18) {
664
        this.fmt.validBitsPerSample = this.read_(buffer, this.uInt16_);
665
        if (this.fmt.chunkSize > 20) {
666
          this.fmt.dwChannelMask = this.read_(buffer, this.uInt32_);
667
          this.fmt.subformat = [
668
            this.read_(buffer, this.uInt32_),
669
            this.read_(buffer, this.uInt32_),
670
            this.read_(buffer, this.uInt32_),
671
            this.read_(buffer, this.uInt32_)];
672
        }
673
      }
674
    }
675
  }
676
677
  /**
678
   * Read the 'fact' chunk of a wav file.
679
   * @param {!Uint8Array} buffer The wav file buffer.
680
   * @param {!Object} signature The file signature.
681
   * @private
682
   */
683
  readFactChunk_(buffer, signature) {
684
    /** @type {?Object} */
685
    let chunk = this.findChunk_(signature, 'fact');
686
    if (chunk) {
687
      this.head_ = chunk.chunkData.start;
688
      this.fact.chunkId = chunk.chunkId;
689
      this.fact.chunkSize = chunk.chunkSize;
690
      this.fact.dwSampleLength = this.read_(buffer, this.uInt32_);
691
    }
692
  }
693
694
  /**
695
   * Read the 'cue ' chunk of a wave file.
696
   * @param {!Uint8Array} buffer The wav file buffer.
697
   * @param {!Object} signature The file signature.
698
   * @private
699
   */
700
  readCueChunk_(buffer, signature) {
701
    /** @type {?Object} */
702
    let chunk = this.findChunk_(signature, 'cue ');
703
    if (chunk) {
704
      this.head_ = chunk.chunkData.start;
705
      this.cue.chunkId = chunk.chunkId;
706
      this.cue.chunkSize = chunk.chunkSize;
707
      this.cue.dwCuePoints = this.read_(buffer, this.uInt32_);
708
      for (let i=0; i<this.cue.dwCuePoints; i++) {
709
        this.cue.points.push({
710
          dwName: this.read_(buffer, this.uInt32_),
711
          dwPosition: this.read_(buffer, this.uInt32_),
712
          fccChunk: this.readString_(buffer, 4),
713
          dwChunkStart: this.read_(buffer, this.uInt32_),
714
          dwBlockStart: this.read_(buffer, this.uInt32_),
715
          dwSampleOffset: this.read_(buffer, this.uInt32_),
716
        });
717
      }
718
    }
719
  }
720
721
  /**
722
   * Read the 'smpl' chunk of a wave file.
723
   * @param {!Uint8Array} buffer The wav file buffer.
724
   * @param {!Object} signature The file signature.
725
   * @private
726
   */
727
  readSmplChunk_(buffer, signature) {
728
    /** @type {?Object} */
729
    let chunk = this.findChunk_(signature, 'smpl');
730
    if (chunk) {
731
      this.head_ = chunk.chunkData.start;
732
      this.smpl.chunkId = chunk.chunkId;
733
      this.smpl.chunkSize = chunk.chunkSize;
734
      this.smpl.dwManufacturer = this.read_(buffer, this.uInt32_);
735
      this.smpl.dwProduct = this.read_(buffer, this.uInt32_);
736
      this.smpl.dwSamplePeriod = this.read_(buffer, this.uInt32_);
737
      this.smpl.dwMIDIUnityNote = this.read_(buffer, this.uInt32_);
738
      this.smpl.dwMIDIPitchFraction = this.read_(buffer, this.uInt32_);
739
      this.smpl.dwSMPTEFormat = this.read_(buffer, this.uInt32_);
740
      this.smpl.dwSMPTEOffset = this.read_(buffer, this.uInt32_);
741
      this.smpl.dwNumSampleLoops = this.read_(buffer, this.uInt32_);
742
      this.smpl.dwSamplerData = this.read_(buffer, this.uInt32_);
743
      for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
744
        this.smpl.loops.push({
745
          dwName: this.read_(buffer, this.uInt32_),
746
          dwType: this.read_(buffer, this.uInt32_),
747
          dwStart: this.read_(buffer, this.uInt32_),
748
          dwEnd: this.read_(buffer, this.uInt32_),
749
          dwFraction: this.read_(buffer, this.uInt32_),
750
          dwPlayCount: this.read_(buffer, this.uInt32_),
751
        });
752
      }
753
    }
754
  }
755
756
  /**
757
   * Read the 'data' chunk of a wave file.
758
   * @param {!Uint8Array} buffer The wav file buffer.
759
   * @param {!Object} signature The file signature.
760
   * @param {boolean} samples True if the samples should be loaded.
761
   * @throws {Error} If no 'data' chunk is found.
762
   * @private
763
   */
764
  readDataChunk_(buffer, signature, samples) {
765
    /** @type {?Object} */
766
    let chunk = this.findChunk_(signature, 'data');
767
    if (chunk) {
768
      this.data.chunkId = 'data';
769
      this.data.chunkSize = chunk.chunkSize;
770
      if (samples) {
771
        this.data.samples = buffer.slice(
772
          chunk.chunkData.start,
773
          chunk.chunkData.end);
774
      }
775
    } else {
776
      throw Error('Could not find the "data" chunk');
777
    }
778
  }
779
780
  /**
781
   * Read the 'bext' chunk of a wav file.
782
   * @param {!Uint8Array} buffer The wav file buffer.
783
   * @param {!Object} signature The file signature.
784
   * @private
785
   */
786
  readBextChunk_(buffer, signature) {
787
    /** @type {?Object} */
788
    let chunk = this.findChunk_(signature, 'bext');
789
    if (chunk) {
790
      this.head_ = chunk.chunkData.start;
791
      this.bext.chunkId = chunk.chunkId;
792
      this.bext.chunkSize = chunk.chunkSize;
793
      this.bext.description = this.readString_(buffer, 256);
794
      this.bext.originator = this.readString_(buffer, 32);
795
      this.bext.originatorReference = this.readString_(buffer, 32);
796
      this.bext.originationDate = this.readString_(buffer, 10);
797
      this.bext.originationTime = this.readString_(buffer, 8);
798
      this.bext.timeReference = [
799
        this.read_(buffer, this.uInt32_),
800
        this.read_(buffer, this.uInt32_)];
801
      this.bext.version = this.read_(buffer, this.uInt16_);
802
      this.bext.UMID = this.readString_(buffer, 64);
803
      this.bext.loudnessValue = this.read_(buffer, this.uInt16_);
804
      this.bext.loudnessRange = this.read_(buffer, this.uInt16_);
805
      this.bext.maxTruePeakLevel = this.read_(buffer, this.uInt16_);
806
      this.bext.maxMomentaryLoudness = this.read_(buffer, this.uInt16_);
807
      this.bext.maxShortTermLoudness = this.read_(buffer, this.uInt16_);
808
      this.bext.reserved = this.readString_(buffer, 180);
809
      this.bext.codingHistory = this.readString_(
810
        buffer, this.bext.chunkSize - 602);
811
    }
812
  }
813
814
  /**
815
   * Read the 'ds64' chunk of a wave file.
816
   * @param {!Uint8Array} buffer The wav file buffer.
817
   * @param {!Object} signature The file signature.
818
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
819
   * @private
820
   */
821
  readDs64Chunk_(buffer, signature) {
822
    /** @type {?Object} */
823
    let chunk = this.findChunk_(signature, 'ds64');
824
    if (chunk) {
825
      this.head_ = chunk.chunkData.start;
826
      this.ds64.chunkId = chunk.chunkId;
827
      this.ds64.chunkSize = chunk.chunkSize;
828
      this.ds64.riffSizeHigh = this.read_(buffer, this.uInt32_);
829
      this.ds64.riffSizeLow = this.read_(buffer, this.uInt32_);
830
      this.ds64.dataSizeHigh = this.read_(buffer, this.uInt32_);
831
      this.ds64.dataSizeLow = this.read_(buffer, this.uInt32_);
832
      this.ds64.originationTime = this.read_(buffer, this.uInt32_);
833
      this.ds64.sampleCountHigh = this.read_(buffer, this.uInt32_);
834
      this.ds64.sampleCountLow = this.read_(buffer, this.uInt32_);
835
      //if (this.ds64.chunkSize > 28) {
836
      //  this.ds64.tableLength = unpack(
837
      //    chunkData.slice(28, 32), this.uInt32_);
838
      //  this.ds64.table = chunkData.slice(
839
      //     32, 32 + this.ds64.tableLength); 
840
      //}
841
    } else {
842
      if (this.container == 'RF64') {
843
        throw Error('Could not find the "ds64" chunk');  
844
      }
845
    }
846
  }
847
848
  /**
849
   * Read the 'LIST' chunks of a wave file.
850
   * @param {!Uint8Array} buffer The wav file buffer.
851
   * @param {!Object} signature The file signature.
852
   * @private
853
   */
854
  readLISTChunk_(buffer, signature) {
855
    /** @type {?Object} */
856
    let listChunks = this.findChunk_(signature, 'LIST', true);
857
    if (listChunks === null) {
858
      return;
859
    }
860
    for (let j=0; j < listChunks.length; j++) {
861
      /** @type {!Object} */
862
      let subChunk = listChunks[j];
863
      this.LIST.push({
864
        chunkId: subChunk.chunkId,
865
        chunkSize: subChunk.chunkSize,
866
        format: subChunk.format,
867
        subChunks: []});
868
      for (let x=0; x<subChunk.subChunks.length; x++) {
869
        this.readLISTSubChunks_(subChunk.subChunks[x],
870
          subChunk.format, buffer);
871
      }
872
    }
873
  }
874
875
  /**
876
   * Read the sub chunks of a 'LIST' chunk.
877
   * @param {!Object} subChunk The 'LIST' subchunks.
878
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
879
   * @param {!Uint8Array} buffer The wav file buffer.
880
   * @private
881
   */
882
  readLISTSubChunks_(subChunk, format, buffer) {
883
    if (format == 'adtl') {
884
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
885
        this.head_ = subChunk.chunkData.start;
886
        /** @type {!Object<string, string|number>} */
887
        let item = {
888
          chunkId: subChunk.chunkId,
889
          chunkSize: subChunk.chunkSize,
890
          dwName: this.read_(buffer, this.uInt32_)
891
        };
892
        if (subChunk.chunkId == 'ltxt') {
893
          item.dwSampleLength = this.read_(buffer, this.uInt32_);
894
          item.dwPurposeID = this.read_(buffer, this.uInt32_);
895
          item.dwCountry = this.read_(buffer, this.uInt16_);
896
          item.dwLanguage = this.read_(buffer, this.uInt16_);
897
          item.dwDialect = this.read_(buffer, this.uInt16_);
898
          item.dwCodePage = this.read_(buffer, this.uInt16_);
899
        }
900
        item.value = this.readZSTR_(buffer, this.head_);
901
        this.LIST[this.LIST.length - 1].subChunks.push(item);
902
      }
903
    // RIFF INFO tags like ICRD, ISFT, ICMT
904
    } else if(format == 'INFO') {
905
      this.head_ = subChunk.chunkData.start;
906
      this.LIST[this.LIST.length - 1].subChunks.push({
907
        chunkId: subChunk.chunkId,
908
        chunkSize: subChunk.chunkSize,
909
        value: this.readZSTR_(buffer,  this.head_)
910
      });
911
    }
912
  }
913
914
  /**
915
   * Read the 'junk' chunk of a wave file.
916
   * @param {!Uint8Array} buffer The wav file buffer.
917
   * @param {!Object} signature The file signature.
918
   * @private
919
   */
920
  readJunkChunk_(buffer, signature) {
921
    /** @type {?Object} */
922
    let chunk = this.findChunk_(signature, 'junk');
923
    if (chunk) {
924
      this.junk = {
925
        chunkId: chunk.chunkId,
926
        chunkSize: chunk.chunkSize,
927
        chunkData: [].slice.call(buffer.slice(
928
          chunk.chunkData.start,
929
          chunk.chunkData.end))
930
      };
931
    }
932
  }
933
934
  /**
935
   * Read bytes as a ZSTR string.
936
   * @param {!Uint8Array} bytes The bytes.
937
   * @return {string} The string.
938
   * @private
939
   */
940
  readZSTR_(bytes, index=0) {
941
    /** @type {string} */
942
    let str = '';
943
    for (let i=index; i<bytes.length; i++) {
944
      this.head_++;
945
      if (bytes[i] === 0) {
946
        break;
947
      }
948
      str += unpackString(bytes, i, 1);
949
    }
950
    return str;
951
  }
952
953
  /**
954
   * Read bytes as a string from a RIFF chunk.
955
   * @param {!Uint8Array} bytes The bytes.
956
   * @param {number} maxSize the max size of the string.
957
   * @return {string} The string.
958
   * @private
959
   */
960
  readString_(bytes, maxSize) {
961
    /** @type {string} */
962
    let str = '';
963
    for (let i=0; i<maxSize; i++) {
964
      str += unpackString(bytes, this.head_, 1);
965
      this.head_++;
966
    }
967
    return str;
968
  }
969
970
  /**
971
   * Read a number from a chunk.
972
   * @param {!Uint8Array} bytes The chunk bytes.
973
   * @param {!Object} bdType The type definition.
974
   * @return {number} The number.
975
   * @private
976
   */
977
  read_(bytes, bdType) {
978
    /** @type {number} */
979
    let size = bdType.bits / 8;
980
    /** @type {number} */
981
    let value = unpackFrom(bytes, bdType, this.head_);
982
    this.head_ += size;
983
    return value;
984
  }
985
986
987
  /**
988
   * Truncate float samples on over and underflow.
989
   * @private
990
   */
991
  truncateSamples(samples) {
992
    if (this.fmt.audioFormat === 3) {
993
      /** @type {number} */   
994
      let len = samples.length;
995
      for (let i=0; i<len; i++) {
996
        if (samples[i] > 1) {
997
          samples[i] = 1;
998
        } else if (samples[i] < -1) {
999
          samples[i] = -1;
1000
        }
1001
      }
1002
    }
1003
  }
1004
}
1005